#include "RenderableObject.h"


RenderableObject::RenderableObject()
{
	objData = new OGLDataObject();
	geomData = new OGLGeometryData();
	vertexData = NULL;
	modelLoaded = false;
}

RenderableObject::RenderableObject(
	OGLDataObject *objectData, 
	OGLGeometryData *geometryData)
{
	if(objectData != NULL)
		objData = objectData;
	else
		objData = new OGLDataObject();
	if(geometryData != NULL)
		geomData = geometryData;
	else
		geomData = new OGLGeometryData();
	
	modelLoaded = false;
	vertexData = NULL;
}

RenderableObject::~RenderableObject()
{
	delete objData;
	delete geomData;
	objData = NULL;
	geomData = NULL;

	if(vertexData != NULL)
	{
		free(vertexData);
		vertexData = NULL;
	}
}

OGLResult RenderableObject::LoadModel(std::string filePath)
{
	if(modelLoaded)
	{
		fprintf(stderr, "[WARNING] This model is already loaded.\n");
		return OGL_WARNING;
	}

	modelLoaded = true;
	std::string inLine;
	int n, i;
	float x, y, z, w, r, g, b, a;

	printf("[DEBUG] Loading model from file : %s\n", filePath.c_str());
	std::ifstream inStream(filePath);
	if(inStream.is_open())
	{
		i = 0;
		n = 0;
		while(inStream.good())
		{
			std::getline(inStream, inLine);
			if(inLine.length() == 0)
				continue;

			else if(inLine[0] == 'n')
			{
				sscanf(inLine.c_str(), "n %d", &n);
				vertexData = (VertexData *)malloc(n * sizeof(VertexData));
				geomData->vertexCount = n;	// Store the vertex count for drawing later
			}
			else if(inLine[0] == 'v')
			{
				sscanf(inLine.c_str(), "v %f %f %f %f", &x, &y, &z, &w);
				std::getline(inStream, inLine);
				if(inLine.length() == 0)
					continue;
				else if(inLine[0] == 'c')
					sscanf(inLine.c_str(), "c %f %f %f %f", &r, &g, &b, &a);

				if(vertexData != NULL && i < n)
				{
					vertexData[i].XYZW[0] = x;
					vertexData[i].XYZW[1] = y;
					vertexData[i].XYZW[2] = z;
					vertexData[i].XYZW[3] = w;

					vertexData[i].RGBA[0] = r;
					vertexData[i].RGBA[1] = g;
					vertexData[i].RGBA[2] = b;
					vertexData[i].RGBA[3] = a;
				}
				i++;
			}
		}
		inStream.close();
	}
	else
	{
		fprintf(stderr, "[WARNING] Could not load model data from file %s.\n", 
			filePath.c_str());
		return OGL_WARNING;
	}

	return createVBO(sizeof(VertexData) * geomData->vertexCount);
}

OGLResult RenderableObject::Render(GLuint activeVAO, GLuint activeProgram)
{
	GLuint inVAOID = activeVAO;
	GLuint inProgramID = activeProgram;

	// Check to see if the right VAO and Program for this object's purposes
	// are in use. If not then bind the right ones and switch them back at
	// the end of the Render call.
	if(inVAOID != objData->vaoID)
		glBindVertexArray(objData->vaoID);
	if(inProgramID != objData->programID)
		glUseProgram(objData->programID);

	// Enable this model's vertex attribute arrays
	std::vector<OGLVAPData *>::iterator it;
	for(it = objData->vertexAttribData.begin();
		it != objData->vertexAttribData.end();
		it++)
	{
		OGLVAPData *vapData = *it;
		glEnableVertexAttribArray(vapData->index);
		// Bind object buffer
		glBindBuffer(objData->dataType, objData->GetVBOID());
		glVertexAttribPointer(
			vapData->index, 
			vapData->size,
			vapData->type,
			vapData->normalized,
			vapData->stride,
			vapData->offset
		);

	}

	// Send the TSR Matrix to the vertex shader program
	glm::mat4 srtMat = geomData->GetSRTMat();
	GLuint tsrMatID = glGetUniformLocation(objData->programID, "SRT");
	glUniformMatrix4fv(tsrMatID, 1, GL_FALSE, &srtMat[0][0]);

	// Draw Array
	glDrawArrays(geomData->drawType, 0, geomData->vertexCount);

	// Disable this model's vertex attribute arrays
	for(it = objData->vertexAttribData.begin();
		it != objData->vertexAttribData.end();
		it++)
	{
		OGLVAPData *vapData = *it;
		glDisableVertexAttribArray(vapData->index);
	}

	// revert to the old active VAO and Program
	if(inVAOID != objData->vaoID)
		glBindVertexArray(inVAOID);
	if(inProgramID != objData->programID)
		glUseProgram(inProgramID);

	return OGL_SUCCESS;
}

OGLResult RenderableObject::Cleanup()
{
	return destroyVOs();
}

OGLResult RenderableObject::createVBO(GLsizeiptr bufferSize)
{
	GLenum errorCheckValue = glGetError();
	GLuint vboid;
	glGenBuffers(1, &vboid); // Identifies 1 vertex buffer
	glBindBuffer(objData->dataType, vboid);	// Now bind the buffer for further function calls

	// Give OpenGl our new vertex information
	glBufferData(objData->dataType, bufferSize, &vertexData[0], objData->usageType);
	
	printf("[DEBUG] New buffer created with size %i.\n", bufferSize);

	errorCheckValue = glGetError();
	if (errorCheckValue != GL_NO_ERROR)
	{
		fprintf(
			stderr,
			"[ERROR] Could not create a VBO: %s \n",
			gluErrorString(errorCheckValue)
		);

		return OGL_ERROR;
	}

	// Store new Buffer ID
	if(objData->SetVBOID(vboid) != OGL_SUCCESS)
	{
		fprintf(stderr, "[ERROR] Could not store VBO ID.\n");
	}

	createVAOPointerData(1, 4, GL_FLOAT, GL_FALSE, sizeof(vertexData[0]), 0);
	createVAOPointerData(2, 4, GL_FLOAT, GL_FALSE, sizeof(vertexData[0]), 
		(GLvoid *)sizeof(vertexData[0].XYZW));

	return OGL_SUCCESS;
}

void  RenderableObject::createVAOPointerData(
	GLuint index, 
	GLuint size, 
	GLenum type, 
	GLboolean normalized,
	GLsizei stride,
	GLvoid *offset)
{
	OGLVAPData *vapData = (OGLVAPData *)malloc(sizeof(OGLVAPData));
	vapData->index = index;
	vapData->normalized = normalized;
	vapData->size = size;
	vapData->type = type;
	vapData->stride = stride;
	vapData->offset = offset;

	glVertexAttribPointer(index, size, type, normalized, stride, offset);
	glEnableVertexAttribArray(index);

//	printf("[DEBUG] Created new VAPData object with : \n\
//\tindex = %u\n\
//\ttype = %u\n\
//\tnormalized = %i\n\
//\tsize = %i\n\
//\tstride = %i\n\
//\toffset = %i\n", vapData->index, vapData->type, 
//	vapData->normalized, vapData->size, vapData->stride, vapData->offset);

	objData->vertexAttribData.push_back(vapData);
}

void RenderableObject::DEBUG_PrintVertexData()
{
	printf("[DEBUG] Printing Buffer Data for VBO : %u\n", objData->GetVBOID());
	printf("[DEBUG] Vertices :\n");
	for(int i = 0; i < geomData->vertexCount; i++)
	{
		printf("\tv%i : %f %f %f %f\n", i + 1, 
			vertexData[i].XYZW[0], 
			vertexData[i].XYZW[1],
			vertexData[i].XYZW[2],
			vertexData[i].XYZW[3]
		);
	}
	printf("[DEBUG] Colors :\n");
	for(int i = 0; i < geomData->vertexCount; i++)
	{
		printf("\tc%i : %f %f %f %f\n", i + 1, 
			vertexData[i].RGBA[0], 
			vertexData[i].RGBA[1],
			vertexData[i].RGBA[2],
			vertexData[i].RGBA[3]
		);
	}
}

OGLResult RenderableObject::destroyVOs()
{
	GLenum errorValue = glGetError();

	glBindVertexArray(objData->vaoID);
	glBindBuffer(objData->dataType, 0);

	std::vector<OGLVAPData *>::iterator it;
	for(it = objData->vertexAttribData.begin();
		it != objData->vertexAttribData.end();
		it++)
	{
		OGLVAPData *vapData = *it;
		glDisableVertexAttribArray(vapData->index);
	}
	GLuint vboid = objData->GetVBOID();
	glDeleteBuffers(1, &vboid);

	errorValue = glGetError();
	if (errorValue != GL_NO_ERROR)
	{
		fprintf(
			stderr,
			"[ERROR] Could not destroy the VBO: %s \n",
			gluErrorString(errorValue)
		);

		return OGL_ERROR;
	}

	return OGL_SUCCESS;
}